Extend one of the built-in exceptions or HttpException directly. Hard-code the status and default message in the constructor so call sites remain clean and self-documenting. Extending the right HTTP semantic subclass (NotFoundException, ConflictException, etc.) makes the intent obvious.
Call sites are clean — no magic strings or repeated status codes across the codebase.
Extending the right subclass makes the HTTP semantic obvious to code reviewers.
A single place to change the message format or add logging logic for that error type.
Filters can catch domain exceptions by type: @Catch(UserNotFoundException).
Extend HttpException directly when no built-in subclass captures the right semantic.